package com.ib.algokit;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.activequant.broker.IOrderTracker;
import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.account.Order;
import org.activequant.core.domainmodel.data.Quote;
import org.activequant.core.domainmodel.events.OrderAcceptEvent;
import org.activequant.core.domainmodel.events.OrderCancelEvent;
import org.activequant.core.domainmodel.events.OrderCommissionEvent;
import org.activequant.core.domainmodel.events.OrderCompletionEvent;
import org.activequant.core.domainmodel.events.OrderEvent;
import org.activequant.core.domainmodel.events.OrderExecutionEvent;
import org.activequant.core.domainmodel.events.OrderSubmitEvent;
import org.activequant.core.domainmodel.events.OrderUpdateEvent;
import org.activequant.core.types.Currency;
import org.activequant.core.types.OrderSide;
import org.activequant.core.types.OrderType;
import org.activequant.core.types.TimeStamp;
import org.activequant.tradesystem.ib.IPositionService;
import org.activequant.util.messaging.ISimpleMessagingService;
import org.activequant.util.pattern.events.IEventListener;
import org.apache.log4j.Logger;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.ui.TextAnchor;

import com.ib.algokit.forex.CurrencyPairs;
import com.ib.algokit.forex.ForexPairStates;
import com.ib.algokit.history.LongPositionOpenedEvent;
import com.ib.algokit.history.ProfitLimitClosedEvent;
import com.ib.algokit.history.ShortPositionOpenedEvent;
import com.ib.algokit.history.StopLossClosedEvent;

/**
 * Opens a position, and if executed, protects it with the trailing stop at a specified distance.<br/>
 * Bracket orders are designed to help limit your loss and lock in a profit by "bracketing" an order with two opposite-side orders. 
 * A BUY order is bracketed by a high-side sell limit order and a low-side sell stop order. 
 * A SELL order is bracketed by a high-side buy stop order and a low side buy limit order.
 * Optionally (if configured) may communicate
 * the entry/exit info to the messenger service.<p>
 * ForexBracketPositionService implements IPositionService.<br/>
 * Holds the following associated variables:
 * <ul>
 * <li>ema(TraderProxy)</li>
 * <li>trailingDistance(double)</li>
 * <li>msg(ISimpleMessagingService)</li>
 * </ul>
 *
 *  @author dmisev
 */
public class ForexBracketPositionService implements IPositionService {
	private final Logger log = Logger.getLogger(getClass());
	/**
	 * private final EMA ema;
	 */
	private final TraderProxy ema;
	/**
	 * private final InstrumentAnalyzer instrumentAnalyzer;
	 */
	private final InstrumentAnalyzer instrumentAnalyzer;
	/**
	 * private double trailingDistance;
	 */
	private double trailingDistance;
	/**
	 * private ISimpleMessagingService msg;
	 */
	private ISimpleMessagingService msg;
	
	private static double totalProfitAndLoss=0;
	
	public enum BracketOrderSide{LIMITPROFIT, STOPLOSS};
	
	/**
	 * constructs a ForexBracketPositionService(implements IPositionService) using the given instrumentAnalyzer(InstrumentAnalyzer) to set its associated one and ema(EMA)
	 * @param instrumentAnalyzer
	 */
	public ForexBracketPositionService(InstrumentAnalyzer instrumentAnalyzer) {
		this.ema = instrumentAnalyzer.getEMA();
		this.instrumentAnalyzer=instrumentAnalyzer;
	}
	
	public static void resetTotalProfitAndLoss(){
		totalProfitAndLoss=0;
	}
	
	/**
	 * Sets the associated trailingDistance(double) with the given val(double)
	 * @param val
	 */
	public void setTrailingDistance(double val) {
		trailingDistance = val;
	}
	/**
	 * Sets the associated msg(ISimpleMessagingService) with the given val(ISimpleMessagingService)
	 * @param val
	 */
	public void setMessengerService(ISimpleMessagingService val) {
		msg = val;
	}
	/**
	 * If the given side(OrderSide) enum is BUY, returns an OrderSide.SELL enum.<br/>
	 * Otherwise, if the given side(OrderSide) enum is SELL, returns an OrderSide.BUY enum.<br/>
	 * Doesn't support any other cases
	 * @param side
	 * @return
	 */
	private static OrderSide reverseSide(OrderSide side) {
		switch(side) {
			case BUY: return OrderSide.SELL;
			case SELL: return OrderSide.BUY;
			default: throw new IllegalArgumentException("unsupported order side: " + side);
		}
	}
	/**
	 * private final static double EPSILON = 1e-6;
	 */
	private final static double EPSILON = 1e-6;
	/**
	 * Returns whether the absolute difference between the given d1(double) and d2(double) is less than the given epsilon(double)<br/>
	 * <code>return Math.abs(d1 - d2) < epsilon;</code>
	 * @param d1
	 * @param d2
	 * @param epsilon
	 * @return
	 */
	private static boolean doubleEquals(double d1, double d2, double epsilon) {
		return Math.abs(d1 - d2) < epsilon;
	}
	/**
	 * A private nested class. Tracker implements IPositionTracker.<br/>
	 * Holds the following associated variables:
	 * <ul>
	 * <li>originalOrder(Order)</li>
	 * <li>mainOrderFilledQuantity(double)</li>
	 * <li>stopOrderFilledQuantity(double)</li>
	 * <li>limitOrderFilledQuantity(double)</li>
	 * <li>mainOrderCommission(double)</li>
	 * <li>stopOrderCommission(double)</li>
	 * <li>limitOrderCommission(double)</li>
	 * <li>mainOrderCommissionCurrency(Currency)</li>
	 * <li>stopOrderCommissionCurrency(Currency)</li>
	 * <li>limitOrderCommissionCurrency(Currency)</li>
	 * <li>mainOrderAveragePrice(double)</li>
	 * <li>averagePriceToClosePosition(double)</li>
	 * <li>isClosed(AtomicBoolean)</li>
	 * <li>mainOrderTracker(IOrderTracker)</li>
	 * <li>stopOrderTracker(IOrderTracker)</li>
	 * <li>limitOrderTracker(IOrderTracker)</li>
	 * <li>mainOrderSubmittedTimeStamp(TimeStamp)</li>
	 * <li>stopOrderSubmittedTimeStamp(TimeStamp)</li>
	 * <li>limitOrderSubmittedTimeStamp(TimeStamp)</li>
	 * <li>mainOrderAcceptedTimeStamp(TimeStamp)</li>
	 * <li>stopOrderAcceptedTimeStamp(TimeStamp)</li>
	 * <li>limitOrderAcceptedTimeStamp(TimeStamp)</li>
	 * <li>mainOrderCompletedAndCommissionsReceivedTimeStamp(TimeStamp)</li>
	 * <li>mainOrderLastExecutionTimeStamp(TimeStamp)</li>
	 * <li>stopOrderLastExecutionTimeStamp(TimeStamp)</li>
	 * <li>limitOrderLastExecutionTimeStamp(TimeStamp)</li>
	 * <li>profitAndLoss(double)</li>
	 * <li>plotMainOrderAcceptEvents(boolean)</li>
	 * <li>plotLimitOrderAcceptEvents(boolean)</li>
	 * <li>plotStopOrderAcceptEvents(boolean)</li>
	 * <li>plotMainOrderExecutionEvents(boolean)</li>
	 * <li>plotLimitOrderExecutionEvents(boolean)</li>
	 * <li>plotStopOrderExecutionEvents(boolean)</li>
	 * <li>plotMainOrderCommissionEvents(boolean)</li>
	 * <li>plotLimitOrderCommissionEvents(boolean)</li>
	 * <li>plotStopOrderCommissionEvents(boolean)</li>
	 * <li>plotMainOrderCancelEvents(boolean)</li>
	 * <li>plotLimitOrderCancelEvents(boolean)</li>
	 * <li>plotStopOrderCancelEvents(boolean)</li>
	 * <li>plotLimitOrderUpdateEvents(boolean)</li>
	 * <li>mainOrderMaximumInactivePeriod(long)</li>
	 * <li>limitOrderMaximumInactivePeriod(long)</li>
	 * <li>stopOrderMaximumInactivePeriod(long)</li>
	 * <li>optimalProfitTarget(double)</li>
	 * <li>limitOrderUpdatedForNoProfit(AtomicBoolean)</li>
	 * <li>mainOrderListener(IEventListener&lt;OrderEvent&gt;)</li>
	 * <li>limitOrderListener(IEventListener&lt;OrderEvent&gt;)</li>
	 * <li>stopOrderListener(IEventListener&lt;OrderEvent&gt;)</li>
	 * </ul>
	 * @author Dimitar
	 *
	 */
	public class Tracker implements IPositionTracker {
		/**
		 * private final Order originalOrder;
		 */
		private final Order originalOrder;
		/**
		 * private double mainOrderFilledQuantity;<br/>
		 * actual quantity of opened position (as per market order execution)
		 */
		private double mainOrderFilledQuantity;
		/**
		 * private double stopOrderFilledQuantity;<br/>
		 * actual quantity of closed position (as per market order execution)
		 */
		private double stopOrderFilledQuantity;
		/**
		 * private double limitOrderFilledQuantity;<br/>
		 * actual quantity of closed position (as per market order execution)
		 */
		private double limitOrderFilledQuantity;
		/**
		 * private double mainOrderCommission=0;<br/>
		 * This is the money I spent (so far) on opening this position in commissions, gets updated every time an execution happens for the main order
		 */
		private double mainOrderCommission=0;
		/**
		 * private double stopOrderCommission=0;
		 */
		private double stopOrderCommission=0;
		/**
		 * private double limitOrderCommission=0;
		 */
		private double limitOrderCommission=0;
		/**
		 * private Currency mainOrderCommissionCurrency=null;
		 */
		private Currency mainOrderCommissionCurrency=null;
		/**
		 * private Currency stopOrderCommissionCurrency=null;
		 */
		private Currency stopOrderCommissionCurrency=null;
		/**
		 * private Currency limitOrderCommissionCurrency=null;
		 */
		private Currency limitOrderCommissionCurrency=null;
		/**
		 * private double mainOrderAveragePrice=0;<br/>
		 * actual price of opened position (without the commission)
		 */
		private double mainOrderAveragePrice=0;
		/**
		 * private double averagePriceToClosePosition=0;
		 */
		private double averagePriceToClosePosition=0;
		/**
		 * private final AtomicBoolean isClosed = new AtomicBoolean();
		 */
		private final AtomicBoolean isClosed = new AtomicBoolean();
		/**
		 * private final IOrderTracker mainOrderTracker;<br/>
		 * the tracker for the main order
		 */
		private final IOrderTracker mainOrderTracker;
		/**
		 * private IOrderTracker stopOrderTracker;
		 */
		private IOrderTracker stopOrderTracker;
		/**
		 * private IOrderTracker limitOrderTracker;
		 */
		private IOrderTracker limitOrderTracker;
		/**
		 * private TimeStamp mainOrderSubmittedTimeStamp=null;<br/>
		 * when was the main order submitted
		 */
		private TimeStamp mainOrderSubmittedTimeStamp=null;
		/**
		 * private TimeStamp stopOrderSubmittedTimeStamp=null;<br/>
		 * when was the stop order submitted
		 */
		private TimeStamp stopOrderSubmittedTimeStamp=null;
		/**
		 * private TimeStamp limitOrderSubmittedTimeStamp=null;<br/>
		 * when was the limit order submitted
		 */
		private TimeStamp limitOrderSubmittedTimeStamp=null;
		/**
		 * private TimeStamp mainOrderAcceptedTimeStamp=null;<br/>
		 * when was the main order submitted
		 */
		private TimeStamp mainOrderAcceptedTimeStamp=null;
		/**
		 * private TimeStamp stopOrderAcceptedTimeStamp=null;<br/>
		 * when was the stop order submitted
		 */
		private TimeStamp stopOrderAcceptedTimeStamp=null;
		/**
		 * private TimeStamp limitOrderAcceptedTimeStamp=null;<br/>
		 * when was the limit order submitted
		 */
		private TimeStamp limitOrderAcceptedTimeStamp=null;
		/**
		 * private TimeStamp mainOrderCanceledTimeStamp=null;<br/>
		 * when was the main order canceled
		 */
		private TimeStamp mainOrderCanceledTimeStamp=null;
		/**
		 * private TimeStamp stopOrderCanceledTimeStamp=null;<br/>
		 * when was the stop order canceled
		 */
		private TimeStamp stopOrderCanceledTimeStamp=null;
		/**
		 * private TimeStamp limitOrderCanceledTimeStamp=null;<br/>
		 * when was the limit order canceled
		 */
		private TimeStamp limitOrderCanceledTimeStamp=null;		
		/**
		 * private TimeStamp mainOrderUpdatedTimeStamp=null;<br/>
		 * when was the main order updated
		 */
		private TimeStamp mainOrderUpdatedTimeStamp=null;
		/**
		 * private TimeStamp stopOrderUpdatedTimeStamp=null;<br/>
		 * when was the stop order updated
		 */
		private TimeStamp stopOrderUpdatedTimeStamp=null;
		/**
		 * private TimeStamp limitOrderUpdatedTimeStamp=null;<br/>
		 * when was the limit order updated
		 */
		private TimeStamp limitOrderUpdatedTimeStamp=null;
		/**
		 * private TimeStamp mainOrderCompletedAndCommissionsReceivedTimeStamp=null;
		 */
		private TimeStamp mainOrderCompletedAndCommissionsReceivedTimeStamp=null;
		/**
		 * private TimeStamp mainOrderLastExecutionTimeStamp=null;
		 */
		private TimeStamp mainOrderLastExecutionTimeStamp=null;
		/**
		 * private TimeStamp stopOrderLastExecutionTimeStamp=null;
		 */
		private TimeStamp stopOrderLastExecutionTimeStamp=null;
		/**
		 * private TimeStamp limitOrderLastExecutionTimeStamp=null;
		 */
		private TimeStamp limitOrderLastExecutionTimeStamp=null;
                /**
		 * private TimeStamp positionOpenedTimeStamp=null;
		 */
		private TimeStamp positionOpenedTimeStamp=null;
		/**
		 * private double profitAndLoss=0;
		 */
		private double profitAndLoss=0;
		/**
		 * private boolean plotMainOrderAcceptEvents=false;
		 */
		private boolean plotMainOrderAcceptEvents=false;
		/**
		 * private boolean plotLimitOrderAcceptEvents=false;
		 */
		private boolean plotLimitOrderAcceptEvents=false;
		/**
		 * private boolean plotStopOrderAcceptEvents=false;
		 */
		private boolean plotStopOrderAcceptEvents=false;
		/**
		 * private boolean plotMainOrderExecutionEvents=false;
		 */
		private boolean plotMainOrderExecutionEvents=false;
		/**
		 * private boolean plotLimitOrderExecutionEvents=false;
		 */
		private boolean plotLimitOrderExecutionEvents=false;
		/**
		 * private boolean plotStopOrderExecutionEvents=false;
		 */
		private boolean plotStopOrderExecutionEvents=false;
		/**
		 * private boolean plotMainOrderCommissionEvents=false;
		 */
		private boolean plotMainOrderCommissionEvents=false;
		/**
		 * private boolean plotLimitOrderCommissionEvents=false;
		 */
		private boolean plotLimitOrderCommissionEvents=false;
		/**
		 * private boolean plotStopOrderCommissionEvents=false;
		 */
		private boolean plotStopOrderCommissionEvents=false;
		/**
		 * private boolean plotMainOrderCancelEvents=false;
		 */
		private boolean plotMainOrderCancelEvents=false;
		/**
		 * private boolean plotLimitOrderCancelEvents=false;
		 */
		private boolean plotLimitOrderCancelEvents=false;
		/**
		 * private boolean plotStopOrderCancelEvents=false;
		 */
		private boolean plotStopOrderCancelEvents=false;
		/**
		 * private boolean plotMainOrderUpdateEvents=false;
		 */
		private boolean plotMainOrderUpdateEvents=false;
		/**
		 * private boolean plotLimitOrderUpdateEvents=false;
		 */
		private boolean plotLimitOrderUpdateEvents=false;
		/**
		 * private boolean plotStopOrderUpdateEvents=false;
		 */
		private boolean plotStopOrderUpdateEvents=false;
		/**
		 * private long mainOrderMaximumInactivePeriod=30*ONE_MINUTE;
		 */
		private long mainOrderMaximumInactivePeriod=30*InstrumentAnalyzer.ONE_MINUTE;//30 min default
		/**
		 * private long limitOrderMaximumInactivePeriod=0;
		 */
		private long limitOrderMaximumInactivePeriod=0;//30*InstrumentAnalyzer.ONE_MINUTE;//30 min default
		/**
		 * private long stopOrderMaximumInactivePeriod=30*ONE_MINUTE;//30 min default
		 */
		private long stopOrderMaximumInactivePeriod=30*InstrumentAnalyzer.ONE_MINUTE;//30 min default
		/**
		 * private long orderSubmitToAcceptTimeoutPeriod=30*ONE_SECOND;//30 sec default
		 */
		private long orderSubmitToAcceptTimeoutPeriod=30*InstrumentAnalyzer.ONE_SECOND;//30 sec default
		/**
		 * private double optimalProfitTarget;
		 */
		private double optimalProfitTarget;
		/**
		 * AtomicBoolean limitOrderUpdatedForNoProfit=new AtomicBoolean(false);
		 */
		AtomicBoolean limitOrderUpdatedForNoProfit=new AtomicBoolean(false);
		/**
		 * private double oneWayMainOrderQuantity = 0;
		 */
		private double oneWayMainOrderQuantity = 0;	
        /**
		 * public final AtomicReference&lt;Order&gt; pendingChainedLimitOrderHolder = new AtomicReference&lt;Order&gt;();
		 */
		public final AtomicReference<Order> pendingChainedLimitOrderHolder = new AtomicReference<Order>();
        /**
		 * public final AtomicReference&lt;Order&gt; pendingChainedStopOrderHolder = new AtomicReference&lt;Order&gt;();
		 */
		public final AtomicReference<Order> pendingChainedStopOrderHolder = new AtomicReference<Order>();
        /**
		 * private boolean simulateOrderUpdatesEnabled=true;
		 */
		private boolean simulateOrderUpdatesEnabled=true;
        /**
         * returns the associated simulateOrderUpdatesEnabled(boolean), hardcoded to true at this time       
         * @return
         */
        public boolean isSimulateOrderUpdatesEnabled(){
             return simulateOrderUpdatesEnabled;
        }
        
        private final List<ForexPairStates> forexPairHistory;
		/**
		 * private final IEventListener&lt;OrderEvent&gt; mainOrderListener = new IEventListener&lt;OrderEvent&gt;() {...}<br/>
		 * A listener whose job is to react on OrderCompletionEvents only. Once it receives a completion(OrderCompletionEvent),
		 * it sets the associated quantity(double) and price(double) with the totalQuantity(double) and averagePrice(double) of that completion(OrderCompletionEvent)<br/>
		 * Then it tries to open a TRAILING_STOP Order to lock in any profits:<br/>
		 * -If no position has been opened already, returns without doing anything else.<br/>
		 * -Otherwise, if we already asked to close the position (and it exists) it will create/submit a MARKET Order to get rid of the position.<br/>
		 * -Otherwise, everything is good and it creates.submits a TRAILING_STOP with trailingDistance=${distance} to lock in the profits if price starts dropping
		 */
		private final IEventListener<OrderEvent> mainOrderListener = new IEventListener<OrderEvent>() {
			public void eventFired(OrderEvent event) {
				//printOrder("Main Order event", event);
				if (event instanceof OrderSubmitEvent) {
					OrderSubmitEvent submit = (OrderSubmitEvent) event;
					mainOrderSubmittedTimeStamp=submit.getEventTimeStamp();
				}else if (event instanceof OrderAcceptEvent) {
					OrderAcceptEvent accepted = (OrderAcceptEvent) event;
					// here the main order has been accepted
					mainOrderAcceptedTimeStamp=accepted.getEventTimeStamp();
					mainOrderCanceledTimeStamp=null;
					if(plotMainOrderAcceptEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=mainOrderTracker.getOrder();
						double price=order.getLimitPrice();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("A", mainOrderAcceptedTimeStamp.getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, accepted, "Main Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}else if (event instanceof OrderCompletionEvent) {
					// here the position has been opened
					OrderCompletionEvent completion = (OrderCompletionEvent) event;
					mainOrderFilledQuantity = completion.getTotalQuantity();
					mainOrderAveragePrice = completion.getAveragePrice();
					//positionCost=quantity*averagePriceToOpenPosition+totalCommissionToOpenPosition;
				}else if (event instanceof OrderExecutionEvent) {
					// here the position has been opened
					OrderExecutionEvent execution = (OrderExecutionEvent) event;
					mainOrderFilledQuantity = execution.getCumulatedQuantity();
					mainOrderAveragePrice = execution.getAveragePrice();
					mainOrderLastExecutionTimeStamp=execution.getEventTimeStamp();
					if(plotMainOrderExecutionEvents&&instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=mainOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("E", mainOrderLastExecutionTimeStamp.getDate().getTime(), mainOrderAveragePrice, -Math.PI/2);
						String tooltip=getOrderEventToolTip(order, execution, "Main Order ", true);
						annotation.setToolTipText(tooltip);
						annotation.setTextAnchor(TextAnchor.BOTTOM_CENTER);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
					//positionCost=quantity*averagePriceToOpenPosition+totalCommissionToOpenPosition;
				}else if (event instanceof OrderCommissionEvent) {
					// here the position has been opened
					OrderCommissionEvent commission = (OrderCommissionEvent) event;
					mainOrderCommission=commission.getCommission();
					mainOrderCommissionCurrency=commission.getCurrency();
					if(commission.isFinalCommissionEvent()){					
//						mainOrderCompletedAndCommissionsReceivedTimeStamp=ema.getTimeStampService().currentTimeStamp();
						//positionCost=quantity*averagePriceToOpenPosition+totalCommissionToOpenPosition;
						mainOrderCompletedAndCommissionsReceivedTimeStamp=commission.getEventTimeStamp();
						String symbol = originalOrder.getInstrumentSpecification().getSymbol().toString();
						Currency quoteCurrency = originalOrder.getInstrumentSpecification().getCurrency();
						double mainOrderCommissionInQuoteCurrency=(quoteCurrency==mainOrderCommissionCurrency)? mainOrderCommission : CurrencyPairs.convert(mainOrderCommissionCurrency, mainOrderCommission, quoteCurrency);//convert commissions from quote currency for my easier bookeeping

							if(originalOrder.getOrderSide()==OrderSide.BUY){
								double totalCost=(mainOrderAveragePrice*mainOrderFilledQuantity) + mainOrderCommissionInQuoteCurrency;
								totalCost = CurrencyPairs.simpleRounding(totalCost, 2, BigDecimal.ROUND_UP);
								//new BigDecimal(Double.toString(totalCost)).toPlainString()
								log.info("bought "+symbol+" "+mainOrderFilledQuantity+" for "+quoteCurrency.toString()+" "+totalCost+" @"+InstrumentAnalyzer.formatTimeStamp(mainOrderCompletedAndCommissionsReceivedTimeStamp));
							}else if(originalOrder.getOrderSide()==OrderSide.SELL){
								double totalCost=(mainOrderAveragePrice*mainOrderFilledQuantity) - mainOrderCommissionInQuoteCurrency;
								totalCost = CurrencyPairs.simpleRounding(totalCost, 2, BigDecimal.ROUND_UP);
								log.info("sold "+symbol+" "+mainOrderFilledQuantity+" and got "+quoteCurrency.toString()+" "+totalCost+" @"+InstrumentAnalyzer.formatTimeStamp(mainOrderCompletedAndCommissionsReceivedTimeStamp));
							}
						
						placeBracketOrders();
					}
					if(plotMainOrderCommissionEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=mainOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("C", commission.getEventTimeStamp().getDate().getTime(), mainOrderAveragePrice, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, commission, "Main Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}else if (event instanceof OrderCancelEvent) {
					// here the position has been opened
					OrderCancelEvent cancel = (OrderCancelEvent) event;
					mainOrderCanceledTimeStamp = cancel.getEventTimeStamp();
					if(plotMainOrderCancelEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=mainOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						double price=order.getLimitPrice();
						XYPointerAnnotation annotation = new XYPointerAnnotation("Cl", cancel.getEventTimeStamp().getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, cancel, "Main Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}else if (event instanceof OrderUpdateEvent) {
					OrderUpdateEvent update = (OrderUpdateEvent) event;
					mainOrderUpdatedTimeStamp = update.getEventTimeStamp();
					if(plotMainOrderUpdateEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
//						Order order=limitOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						double price=update.getUpdatedOrder().getStopPrice();
						XYPointerAnnotation annotation = new XYPointerAnnotation("U", update.getEventTimeStamp().getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						Order updatedOrder=update.getUpdatedOrder();
						String tooltip=getOrderEventToolTip(updatedOrder, update, "Main Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}
			}
		};
		/**
		 * private final IEventListener&lt;OrderEvent&gt; stopOrderListener = new IEventListener&lt;OrderEvent&gt;() {...}<br/>
		 * A listener whose job is to wait for an OrderCompletionEvent whose total totalQuantity matches the associated quantity(double) (up to a very small difference is ok).<br/>
		 * If they match we have happily closed the position, it sets the isClosed(AtomicBoolean) to true and sends a short report to the user.<br/>
		 * Otherwise it sends the bad news "shark alert" message to the user since it didn't get a full position close.
		 * 
		 */
		private final IEventListener<OrderEvent> stopOrderListener = new IEventListener<OrderEvent>() {
			/**
			 * <strong>1.</strong> The given event(OrderEvent) has to be an OrderCompletionEvent, otherwise it returns without doing anything.<br/>
			 * <strong>2.</strong> Gets completion(OrderCompletionEvent) by casting the given event(OrderEvent) into an OrderCompletionEvent<br/>
			 * <strong>3.</strong>If the difference between the associated quantity(double) and the totalQuantity(double) of the completion(OrderCompletionEvent) is 
			 * small enough that we can say the are identical:<br/>
			 * - sets the value of the associated isClosed(AtomicBoolean) to true<br/>
			 * - if the msg(ISimpleMessagingService) is not null, it uses it to send a message whose subj="position closed" and a body in the form:<br/>
			 * <strong>closed<br/>
			 * ${originalOrder}<br/>
			 * averagePrice=${completion.averagePrice}</strong><br/>
			 * <strong>4.</strong> Otherwise, If the difference is not that small (we failed to close the order completely or partially):<br/>
			 * - if the msg(ISimpleMessagingService) is not null, it uses it to send a message whose subj="shark alert" and a body in the form:<br/>
			 * <strong>failed to close position<br/>
			 * ${originalOrder}</strong>
			 * @param e
			 */
			public void eventFired(OrderEvent event) {
				if (event instanceof OrderSubmitEvent) {
					OrderSubmitEvent submit = (OrderSubmitEvent) event;
					stopOrderSubmittedTimeStamp=submit.getEventTimeStamp();		
					stopOrderCanceledTimeStamp=null;
				}else if (event instanceof OrderAcceptEvent) {
					OrderAcceptEvent accepted = (OrderAcceptEvent) event;
					// here the stop order has been accepted
					stopOrderAcceptedTimeStamp=accepted.getEventTimeStamp();
					stopOrderCanceledTimeStamp=null;
					if(plotStopOrderAcceptEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=stopOrderTracker.getOrder();
						double price=order.getStopPrice();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("A", stopOrderAcceptedTimeStamp.getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, accepted, "Stop Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}else if (event instanceof OrderExecutionEvent) {
					// here the position has been opened
					OrderExecutionEvent execution = (OrderExecutionEvent) event;
//					quantity = execution.getCumulatedQuantity();
					averagePriceToClosePosition = execution.getAveragePrice();
					stopOrderLastExecutionTimeStamp=execution.getEventTimeStamp();
					stopOrderFilledQuantity = execution.getCumulatedQuantity();
					if(plotStopOrderExecutionEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=stopOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("E", stopOrderLastExecutionTimeStamp.getDate().getTime(), averagePriceToClosePosition, -Math.PI/2);
						String tooltip=getOrderEventToolTip(order, execution, "Stop Order ", true);
						annotation.setToolTipText(tooltip);
						annotation.setTextAnchor(TextAnchor.BOTTOM_CENTER);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);						
					}
				}else if (event instanceof OrderCommissionEvent) {
					OrderCommissionEvent commission = (OrderCommissionEvent) event;
					stopOrderCommission=commission.getCommission();
					stopOrderCommissionCurrency=commission.getCurrency();
                    TimeStamp timeStamp=commission.getEventTimeStamp();
					if(commission.isFinalCommissionEvent()){
//						String symbol = originalOrder.getInstrumentSpecification().getSymbol().toString();					
						Currency quoteCurrency = originalOrder.getInstrumentSpecification().getCurrency();
						double mainOrderCommissionInQuoteCurrency=(quoteCurrency==mainOrderCommissionCurrency)? mainOrderCommission : CurrencyPairs.convert(mainOrderCommissionCurrency, mainOrderCommission, quoteCurrency);//convert commissions from quote currency for my easier bookeeping
						double stopOrderCommissionInQuoteCurrency=0;
						if(stopOrderCommissionCurrency!=null){
							stopOrderCommissionInQuoteCurrency=(quoteCurrency==stopOrderCommissionCurrency)? stopOrderCommission : CurrencyPairs.convert(stopOrderCommissionCurrency, stopOrderCommission, quoteCurrency);//convert commissions from quote currency for my easier bookeeping
						}
						double limitOrderCommissionInQuoteCurrency=0;
						if(limitOrderCommissionCurrency!=null){
							limitOrderCommissionInQuoteCurrency=(quoteCurrency==limitOrderCommissionCurrency)? limitOrderCommission : CurrencyPairs.convert(limitOrderCommissionCurrency, limitOrderCommission, quoteCurrency);//convert commissions from quote currency for my easier bookeeping
						}						
						double totalCommissionsInQuoteCurrency=mainOrderCommissionInQuoteCurrency+stopOrderCommissionInQuoteCurrency+limitOrderCommissionInQuoteCurrency;
						if(originalOrder.getOrderSide()==OrderSide.BUY){
							profitAndLoss=-(mainOrderAveragePrice*mainOrderFilledQuantity + totalCommissionsInQuoteCurrency) + (averagePriceToClosePosition*mainOrderFilledQuantity);
						}else{
							profitAndLoss=(mainOrderAveragePrice*mainOrderFilledQuantity) - (averagePriceToClosePosition*mainOrderFilledQuantity + totalCommissionsInQuoteCurrency);
						}
						StringBuffer message=new StringBuffer();
						InstrumentSpecification instrumentSpecification=originalOrder.getInstrumentSpecification();
						String pair=instrumentSpecification.getSymbol().toString()+"/"+instrumentSpecification.getCurrency();
						message.append("closed position "+pair+" with STOP order.");
						double profitAndLossRounded=CurrencyPairs.simpleRounding(profitAndLoss, 2, BigDecimal.ROUND_DOWN);
                        double profitAndLossUSD=quoteCurrency==Currency.USD? profitAndLoss : CurrencyPairs.convert(quoteCurrency, profitAndLoss, Currency.USD);
						double profitAndLossUSDRounded=CurrencyPairs.simpleRounding(profitAndLossUSD, 2, BigDecimal.ROUND_DOWN);
                        if(quoteCurrency!=Currency.USD){
							totalProfitAndLoss+=profitAndLossUSD;
							message.append("P&L:"+quoteCurrency.toString()+" "+profitAndLossRounded+" (USD "+profitAndLossUSDRounded+")");
						}else{
							totalProfitAndLoss+=profitAndLoss;
							message.append("P&L:"+quoteCurrency.toString()+" "+profitAndLossRounded);
						}

                        double totalProfitAndLossRounded=CurrencyPairs.simpleRounding(totalProfitAndLoss, 2, BigDecimal.ROUND_DOWN);
                        message.append(" total P&L:"+Currency.USD+" "+totalProfitAndLossRounded);
                        message.append(" @"+InstrumentAnalyzer.formatTimeStamp(commission.getEventTimeStamp()));
                        log.info(message.toString());
                        
//						mainOrderCompletedAndCommissionsReceivedTimeStamp=ema.getTimeStampService().currentTimeStamp();
						//positionCost=quantity*averagePriceToOpenPosition+totalCommissionToOpenPosition;
						if(msg != null) {
							msg.sendMessage("position closed", "closed\n" + originalOrder + "\n" + "averagePrice=" + averagePriceToClosePosition+" profitAndLoss="+profitAndLossRounded);
						}
						
						if(ForexAnalyzerUtils.isHistoryTrackingEnabled()){
							Currency baseCurrency=Currency.toClass(instrumentSpecification.getSymbol().toString());
							OrderSide originalOrderSide=originalOrder.getOrderSide();
							Integer stopLossPips = CurrencyPairs.getBracketPipsForPair(baseCurrency, quoteCurrency, BracketOrderSide.STOPLOSS, originalOrderSide);
							Integer limitProfitPips = CurrencyPairs.getBracketPipsForPair(baseCurrency, quoteCurrency, BracketOrderSide.LIMITPROFIT, originalOrderSide);
							long realizedStopLossPips =0;
							String msg=null;
							if(originalOrderSide==OrderSide.BUY){
								realizedStopLossPips = Math.round(((mainOrderAveragePrice/averagePriceToClosePosition)-1)/CurrencyPairs.PIP);
								msg = "long stop loss";
							}else{
								realizedStopLossPips = Math.round((1-(mainOrderAveragePrice/averagePriceToClosePosition))/CurrencyPairs.PIP);
								msg = "short stop loss";
							}
							double profitLossRatio=stopLossPips/limitProfitPips;
							double lossRatio=-(double)realizedStopLossPips/stopLossPips;
							lossRatio*=profitLossRatio;
                            ForexPairStates latestForexPairStates=forexPairHistory.get(forexPairHistory.size()-1);
//                            long div=timeStamp.getNanoseconds()/1000000;
//        					if(/**"NZD/CHF".equals(thisForexPair)&&**/div==1296587951796L){
//        						System.out.println("he");
//        					}else if(/**"NZD/JPY".equals(thisForexPair)&&**/div==1302710562606L){
//        						System.out.println("he");
//        					}
                            StopLossClosedEvent stopLossClosedEvent=new StopLossClosedEvent(timeStamp, positionOpenedTimeStamp, msg/**"lpPips="+limitProfitPips+" rlpPips="+realizedlimitProfitPips**/, instrumentSpecification, latestForexPairStates, profitAndLossUSDRounded, lossRatio);
//							if(ForexAnalyzerUtils.isHistoryTrackingEnabled()){
								ForexAnalyzerUtils.addHistory(stopLossClosedEvent);	
//							}

						}
                                               
					}
					if(plotStopOrderCommissionEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=stopOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("C", commission.getEventTimeStamp().getDate().getTime(), mainOrderAveragePrice, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, commission, "Stop Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}else if (event instanceof OrderCancelEvent) {
					OrderCancelEvent cancel = (OrderCancelEvent) event;
//                    Order updatedStopOrder = pendingSimulatedStopUpdate.getAndSet(null);
//                    if(isSimulateOrderUpdatesEnabled() && updatedStopOrder!=null){// simulated update: stop order cancelled, now place the updated order
//                    	stopOrderTracker = ema.getBroker().prepareOrder(updatedStopOrder);
//                        stopOrderTracker.getOrderEventSource().addEventListener(stopOrderListener);
//                        stopOrderTracker.submit();
//                        return;
//                    }                
					stopOrderCanceledTimeStamp = cancel.getEventTimeStamp();
					if(plotStopOrderCancelEvents&&instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=stopOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						double price=order.getStopPrice();
						XYPointerAnnotation annotation = new XYPointerAnnotation("Cl", cancel.getEventTimeStamp().getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, cancel, "Stop Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);						
					}
				}else if(event instanceof OrderCompletionEvent) {
					OrderCompletionEvent completion = (OrderCompletionEvent) event;
					stopOrderFilledQuantity = completion.getTotalQuantity();
					Order updatedStopOrder = pendingChainedStopOrderHolder.getAndSet(null);
					/**
					 * if there is a chained stop order pending, activate it after confirming cancellation of this stop order 
					 */
                    if(updatedStopOrder!=null  && (completion.getTerminalError() instanceof OrderCancelEvent)){// simulated update: stop order cancelled, now place the updated order
                    	stopOrderTracker = ema.getBroker().prepareOrder(updatedStopOrder);
                        stopOrderTracker.getOrderEventSource().addEventListener(stopOrderListener);
                        stopOrderTracker.submit();
                        return;
                    }
					if(doubleEquals(mainOrderFilledQuantity, completion.getTotalQuantity(), EPSILON)) {
						// all ok, position has been closed
						isClosed.set(true);
						averagePriceToClosePosition=completion.getAveragePrice();
						if(limitOrderTracker!=null){
							limitOrderTracker.cancel();
						}else{
							log.error("weird limitOrderTracker is null, shouldn't happen" + originalOrder);
						}
					} else if(!isClosed.get()){//doesn't handle partial execution
						// oops. failed to close!
						// abnormal condition. cry for human intervention
						log.error("shark alert: failed to close position: " + originalOrder);
						if(msg != null) {
							msg.sendMessage("shark alert", "failed to close position\n" + originalOrder);
						}
					}
				}else if (event instanceof OrderUpdateEvent) {
					OrderUpdateEvent update = (OrderUpdateEvent) event;
					stopOrderUpdatedTimeStamp = update.getEventTimeStamp();
					if(plotStopOrderUpdateEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
//						Order order=limitOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						double price=update.getUpdatedOrder().getStopPrice();
						XYPointerAnnotation annotation = new XYPointerAnnotation("U", update.getEventTimeStamp().getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						Order updatedOrder=update.getUpdatedOrder();
						String tooltip=getOrderEventToolTip(updatedOrder, update, "Stop Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}
			}
		};
		
		/**
		 * private final IEventListener&lt;OrderEvent&gt; limitOrderListener = new IEventListener&lt;OrderEvent&gt;() {...}<br/>
		 * A listener (to lock in profits) whose job is to wait for an OrderCompletionEvent whose total totalQuantity matches the associated quantity(double) (up to a very small difference is ok).<br/>
		 * If they match we have happily closed the position, it sets the isClosed(AtomicBoolean) to true and sends a short report to the user.<br/>
		 * Otherwise it sends the bad news "shark alert" message to the user since it didn't get a full position close.
		 * 
		 */
		private final IEventListener<OrderEvent> limitOrderListener = new IEventListener<OrderEvent>() {
			/**
			 * <strong>1.</strong> The given event(OrderEvent) has to be an OrderCompletionEvent, otherwise it returns without doing anything.<br/>
			 * <strong>2.</strong> Gets completion(OrderCompletionEvent) by casting the given event(OrderEvent) into an OrderCompletionEvent<br/>
			 * <strong>3.</strong>If the difference between the associated quantity(double) and the totalQuantity(double) of the completion(OrderCompletionEvent) is 
			 * small enough that we can say the are identical:<br/>
			 * - sets the value of the associated isClosed(AtomicBoolean) to true<br/>
			 * - if the msg(ISimpleMessagingService) is not null, it uses it to send a message whose subj="position closed" and a body in the form:<br/>
			 * <strong>closed<br/>
			 * ${originalOrder}<br/>
			 * averagePrice=${completion.averagePrice}</strong><br/>
			 * <strong>4.</strong> Otherwise, If the difference is not that small (we failed to close the order completely or partially):<br/>
			 * - if the msg(ISimpleMessagingService) is not null, it uses it to send a message whose subj="shark alert" and a body in the form:<br/>
			 * <strong>failed to close position<br/>
			 * ${originalOrder}</strong>
			 * @param e
			 */
			public void eventFired(OrderEvent event) {
				if (event instanceof OrderSubmitEvent) {
					OrderSubmitEvent submit = (OrderSubmitEvent) event;
					limitOrderSubmittedTimeStamp=submit.getEventTimeStamp();
					limitOrderCanceledTimeStamp=null;
				}else if (event instanceof OrderAcceptEvent) {
					OrderAcceptEvent accepted = (OrderAcceptEvent) event;
					// here the limit order has been accepted
					limitOrderAcceptedTimeStamp=accepted.getEventTimeStamp();
					limitOrderCanceledTimeStamp=null;
					if(plotLimitOrderAcceptEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=limitOrderTracker.getOrder();
						double price=order.getLimitPrice();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("A", limitOrderAcceptedTimeStamp.getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, accepted, "Limit Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);						
					}
				}else if (event instanceof OrderExecutionEvent) {
					// here the position has been opened
					OrderExecutionEvent execution = (OrderExecutionEvent) event;
//					quantity = execution.getCumulatedQuantity();
					averagePriceToClosePosition = execution.getAveragePrice();
					limitOrderLastExecutionTimeStamp=execution.getEventTimeStamp();
					limitOrderFilledQuantity = execution.getCumulatedQuantity();
					if(plotLimitOrderExecutionEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=limitOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("E", limitOrderLastExecutionTimeStamp.getDate().getTime(), averagePriceToClosePosition, -Math.PI/2);
						String tooltip=getOrderEventToolTip(order, execution, "Limit Order ", true);
						annotation.setToolTipText(tooltip);
						annotation.setTextAnchor(TextAnchor.BOTTOM_CENTER);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}else if (event instanceof OrderCommissionEvent) {
					OrderCommissionEvent commission = (OrderCommissionEvent) event;
					limitOrderCommission=commission.getCommission();
					limitOrderCommissionCurrency=commission.getCurrency();
                    TimeStamp timeStamp=commission.getEventTimeStamp();
					if(commission.isFinalCommissionEvent())
					{
//						String symbol = originalOrder.getInstrumentSpecification().getSymbol().toString();
						Currency quoteCurrency = originalOrder.getInstrumentSpecification().getCurrency();
						double mainOrderCommissionInQuoteCurrency=(quoteCurrency==mainOrderCommissionCurrency)? mainOrderCommission : CurrencyPairs.convert(mainOrderCommissionCurrency, mainOrderCommission, quoteCurrency);//convert commissions from quote currency for my easier bookeeping
						double stopOrderCommissionInQuoteCurrency=0;
						if(stopOrderCommissionCurrency!=null){
							stopOrderCommissionInQuoteCurrency=(quoteCurrency==stopOrderCommissionCurrency)? stopOrderCommission : CurrencyPairs.convert(stopOrderCommissionCurrency, stopOrderCommission, quoteCurrency);//convert commissions from quote currency for my easier bookeeping
						}
						double limitOrderCommissionInQuoteCurrency=0;
						if(limitOrderCommissionCurrency!=null){
							limitOrderCommissionInQuoteCurrency=(quoteCurrency==limitOrderCommissionCurrency)? limitOrderCommission : CurrencyPairs.convert(limitOrderCommissionCurrency, limitOrderCommission, quoteCurrency);//convert commissions from quote currency for my easier bookeeping
						}	
						double totalCommissionsInQuoteCurrency=mainOrderCommissionInQuoteCurrency+stopOrderCommissionInQuoteCurrency+limitOrderCommissionInQuoteCurrency;
						if(originalOrder.getOrderSide()==OrderSide.BUY){
							profitAndLoss=-(mainOrderAveragePrice*mainOrderFilledQuantity + totalCommissionsInQuoteCurrency) + (averagePriceToClosePosition*mainOrderFilledQuantity);
						}else{
							profitAndLoss=(mainOrderAveragePrice*mainOrderFilledQuantity ) - (averagePriceToClosePosition*mainOrderFilledQuantity + totalCommissionsInQuoteCurrency);
						}
						InstrumentSpecification instrumentSpecification=originalOrder.getInstrumentSpecification();
						String pair=instrumentSpecification.getSymbol().toString()+"/"+instrumentSpecification.getCurrency();
						StringBuffer message=new StringBuffer();
						message.append("closed position "+pair+" with LIMIT order.");
						double profitAndLossRounded=CurrencyPairs.simpleRounding(profitAndLoss, 2, BigDecimal.ROUND_DOWN);
                        double profitAndLossUSD=(quoteCurrency==Currency.USD)? profitAndLoss : CurrencyPairs.convert(quoteCurrency, profitAndLoss, Currency.USD);
						double profitAndLossUSDRounded=CurrencyPairs.simpleRounding(profitAndLossUSD, 2, BigDecimal.ROUND_DOWN);
                        if(quoteCurrency!=Currency.USD){
							totalProfitAndLoss+=profitAndLossUSD;
							message.append("P&L:"+quoteCurrency.toString()+" "+profitAndLossRounded+" (USD "+profitAndLossUSDRounded+")");
						}else{
							totalProfitAndLoss+=profitAndLoss;
							message.append("P&L:"+quoteCurrency.toString()+" "+profitAndLossRounded);
						}
                        double totalProfitAndLossRounded=CurrencyPairs.simpleRounding(totalProfitAndLoss, 2, BigDecimal.ROUND_DOWN);
                        message.append(" total P&L:"+Currency.USD+" "+totalProfitAndLossRounded);
                        message.append(" @"+InstrumentAnalyzer.formatTimeStamp(timeStamp));
                        log.info(message.toString());				
						if(msg != null) {
							msg.sendMessage("position closed", "closed\n" + originalOrder + "\n" + "averagePrice=" + averagePriceToClosePosition+" profitAndLoss="+profitAndLossRounded);
						}
						if(ForexAnalyzerUtils.isHistoryTrackingEnabled()){
							Currency baseCurrency=Currency.toClass(instrumentSpecification.getSymbol().toString());
							OrderSide originalOrderSide = originalOrder.getOrderSide();
							Integer limitProfitPips = CurrencyPairs.getBracketPipsForPair(baseCurrency, quoteCurrency, BracketOrderSide.LIMITPROFIT, originalOrderSide);
							long realizedlimitProfitPips =0;
							String msg=null;
							if(originalOrderSide==OrderSide.BUY){
								realizedlimitProfitPips = Math.round(((averagePriceToClosePosition/mainOrderAveragePrice)-1)/CurrencyPairs.PIP);
								msg = "long profit limit";
							}else{
								realizedlimitProfitPips = Math.round((1-(averagePriceToClosePosition/mainOrderAveragePrice))/CurrencyPairs.PIP);
								msg = "short profit limit";
							}
							double successRatio=(double)realizedlimitProfitPips/limitProfitPips;
							ForexPairStates latestForexPairStates=forexPairHistory.get(forexPairHistory.size()-1);
							ProfitLimitClosedEvent profitLimitClosedEvent=new ProfitLimitClosedEvent(timeStamp, positionOpenedTimeStamp, msg, instrumentSpecification, latestForexPairStates, profitAndLossUSDRounded, successRatio);
							ForexAnalyzerUtils.addHistory(profitLimitClosedEvent);
						}
					}
					if(plotLimitOrderCommissionEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=limitOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						XYPointerAnnotation annotation = new XYPointerAnnotation("C", timeStamp.getDate().getTime(), mainOrderAveragePrice, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, commission, "Limit Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}					
				}else if (event instanceof OrderCancelEvent) {
					OrderCancelEvent cancel = (OrderCancelEvent) event;
//                    Order updatedLimitOrder = pendingSimulatedLimitUpdate.getAndSet(null);
//                    if(isSimulateOrderUpdatesEnabled() && updatedLimitOrder!=null){// simulated update: limit order cancelled, now place the updated order
//                    	limitOrderTracker = ema.getBroker().prepareOrder(updatedLimitOrder);
//                    	limitOrderTracker.getOrderEventSource().addEventListener(limitOrderListener);
//                    	limitOrderTracker.submit();
//                    	return;
//                    }
					limitOrderCanceledTimeStamp = cancel.getEventTimeStamp();
					if(plotLimitOrderCancelEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
						Order order=limitOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						double price=order.getLimitPrice();
						XYPointerAnnotation annotation = new XYPointerAnnotation("Cl", cancel.getEventTimeStamp().getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						String tooltip=getOrderEventToolTip(order, cancel, "Limit Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}else if (event instanceof OrderCompletionEvent) {
					OrderCompletionEvent completion = (OrderCompletionEvent) event;
					limitOrderFilledQuantity = completion.getTotalQuantity();
					Order updatedLimitOrder = pendingChainedLimitOrderHolder.getAndSet(null);
                    /**
                     * if there is a chained limit order pending, activate it after confirming cancellation of this limit order 
                     */
					if(updatedLimitOrder!=null && (completion.getTerminalError() instanceof OrderCancelEvent)){// simulated update: limit order cancelled, now place the updated order
                    	limitOrderTracker = ema.getBroker().prepareOrder(updatedLimitOrder);
                    	limitOrderTracker.getOrderEventSource().addEventListener(limitOrderListener);
                    	limitOrderTracker.submit();
                    	return;
                    }
					
					if(doubleEquals(mainOrderFilledQuantity, completion.getTotalQuantity(), EPSILON)) {
						// all ok, position has been closed
						isClosed.set(true);
						averagePriceToClosePosition=completion.getAveragePrice();
						if(stopOrderTracker!=null){
							stopOrderTracker.cancel();
						}else{
							log.error("weird stopOrderTracker is null, shouldn't happen" + originalOrder);
						}
					} else if(!isClosed.get()){//doesn't handle partial execution
						// oops. failed to close!
						// abnormal condition. cry for human intervention
						log.error("shark alert: failed to close position: " + originalOrder);
						if(msg != null) {
							msg.sendMessage("shark alert", "failed to close position\n" + originalOrder);
						}
					}
				}else if (event instanceof OrderUpdateEvent) {
					OrderUpdateEvent update = (OrderUpdateEvent) event;
					limitOrderUpdatedTimeStamp = update.getEventTimeStamp();
					if(plotLimitOrderUpdateEvents && instrumentAnalyzer.getAnalyzerPlugin().getTimeseriesCombinedChart()!=null){
//						Order order=limitOrderTracker.getOrder();
						AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
						double price=update.getUpdatedOrder().getLimitPrice();
						XYPointerAnnotation annotation = new XYPointerAnnotation("U", update.getEventTimeStamp().getDate().getTime(), price, 1.570796326794897D);
						annotation.setTextAnchor(TextAnchor.TOP_CENTER);
						Order updatedOrder=update.getUpdatedOrder();
						String tooltip=getOrderEventToolTip(updatedOrder, update, "Limit Order ", true);
						annotation.setToolTipText(tooltip);
						analyzerUtils.getTimeseriesCombinedChart().addAnnotation(annotation);
					}
				}
			}
		};		
		/**
		 * If no position has been opened already, returns without doing anything else.<br/>
		 * Otherwise, if we already asked to close the position (and it exists) it will create/submit a MARKET Order to get rid of the position.<br/>
		 * Otherwise, everything is good and it creates/submits a TRAILING_STOP with trailingDistance=${distance} to lock in the profits if price starts dropping:<br/>
		 * <strong>1.</strong> If the associated quantity(double) is 0, it sets the value of the associated isClosed(AtomicBoolean) to true 
		 * and returns without doing anything else (a position wasn't even opened in this case, no reason to continue) <br/>
		 * <strong>2.</strong> If the msg(ISimpleMessagingService) is not null, it uses it to send a message whose subj="position opened" and a body in the form:<br/>
		 * <strong>opened: quantity=${quantity}, averagePrice=${averagePrice}<br/>
		 * ${originalOrder}</strong><br/>
		 * <strong>3.</strong> If the associated value of the associated isClosed(AtomicBoolean) id true:<br/>
		 * - if the associated quantity(double) is 0 or less, it returns without doing anything else.<br/>
		 * -Creates a MARKET order(Order) whose instrumentSpecification(InstrumentSpecification) is same as the one in the associated originalOrder(Order), an opposite OrderSide 
		 * from the one of the associated originalOrder(Order), and for the associated quantity(double).<br/>
		 * - validates and prepares that order(Order) for submission by invoking <code>broker.prepareOrder(order)</code> on the broker(IBroker) and sets the associated stopOrderTracker(IOrderTracker) 
		 * with the resulting IOrderTracker.<br/>
		 * - registers the associated stopOrderListener(IEventListener&lt;OrderEvent&gt;) to listen for OrderEvents from the associated stopOrderTracker(IOrderTracker) <br/>
		 * - submits the order by invoking <code>stopOrderTracker.submit()</code> on the associated stopOrderTracker(IOrderTracker) and then returns without doing anything else.<br/>
		 * <strong>4.</strong> Otherwise (everything is normal), creates a TRAILING_STOP order(Order) whose instrumentSpecification(InstrumentSpecification) is same as the one in the associated originalOrder(Order)
		 * , an opposite OrderSide from the one of the associated originalOrder(Order), and for the associated quantity(double). Its trailingDistance(double) is set with the distance(double).<br/>
		 * - validates and prepares that order(Order) for submission by invoking <code>broker.prepareOrder(order)</code> on the broker(IBroker) and sets the associated stopOrderTracker(IOrderTracker) 
		 * with the resulting IOrderTracker.<br/>
		 * - registers the associated stopOrderListener(IEventListener&lt;OrderEvent&gt;) to listen for OrderEvents from the associated stopOrderTracker(IOrderTracker) <br/>
		 * - submits the order by invoking <code>stopOrderTracker.submit()</code> on the associated stopOrderTracker(IOrderTracker).
		 */
		public void placeBracketOrders() {
			InstrumentSpecification instrumentSpecification=originalOrder.getInstrumentSpecification();
			String symbol=instrumentSpecification.getSymbol().toString();
			Currency currency=instrumentSpecification.getCurrency();
			String title=symbol+"/"+currency.toString();
			double quantityToClose=mainOrderFilledQuantity - oneWayMainOrderQuantity;
			positionOpenedTimeStamp=ema.getTimeStampService().currentTimeStamp();
			if( quantityToClose == 0.0) {
				// position was not opened, see logs for more details
				isClosed.set(true);
					log.info(title+" [placeBracketOrders] main order quantityToClose is 0, nothing to do"+" @"+InstrumentAnalyzer.formatTimeStamp(positionOpenedTimeStamp));
				return;
			}
			//log.info("opened position: quantity=" + quantity + ", averagePrice=" + averagePrice + "," + originalOrder);
			if(msg != null) {
				msg.sendMessage("position opened", "opened: quantity=" + mainOrderFilledQuantity + ", averagePrice=" + mainOrderAveragePrice + "\n" + originalOrder);
			}
			if(isClosed.get()) {
				// position has been closed (requested to be closed)
				// before we had a chance to issue trailing stop
				// if any outstanding amount, lets close it
				if(quantityToClose <= 0.0) {
					log.info(title+" [placeBracketOrders] position has been closed (requested to be closed) before we had a chance to issue closing order"+" @"+InstrumentAnalyzer.formatTimeStamp(positionOpenedTimeStamp));
					return;
				}
				log.info(title+" [placeBracketOrders] placing MARKET order to close position because quantityToClose="+quantityToClose+" @"+InstrumentAnalyzer.formatTimeStamp(positionOpenedTimeStamp));
				Order order = new Order();
				order.setOrderType(OrderType.MARKET);
				order.setInstrumentSpecification(instrumentSpecification);
				order.setQuantity(quantityToClose);
				order.setOrderSide(reverseSide(originalOrder.getOrderSide()));
				stopOrderTracker = ema.getBroker().prepareOrder(order);
				stopOrderTracker.getOrderEventSource().addEventListener(stopOrderListener);
				stopOrderTracker.submit();
				return;
			}
			// normal stuff: position opened, lets put a trailing stop
			OrderSide originalOrderSide = originalOrder.getOrderSide();
			OrderSide reverseSide=reverseSide(originalOrderSide);
			//double commission=FinancialLibrary.maxOf(1d, totalCommission);			
			double lowerPrice=0;
			double upperPrice=0;
			Currency baseCurrency=Currency.toClass(symbol);
			Integer limitProfitPips=CurrencyPairs.getBracketPipsForPair(baseCurrency, currency, BracketOrderSide.LIMITPROFIT, originalOrderSide);
			Integer stopLossPips = CurrencyPairs.getBracketPipsForPair(baseCurrency, currency, BracketOrderSide.STOPLOSS, originalOrderSide);
			if(originalOrderSide==OrderSide.SELL){
				lowerPrice=mainOrderAveragePrice*(1-(limitProfitPips*CurrencyPairs.PIP));
				upperPrice=mainOrderAveragePrice*(1+(stopLossPips*CurrencyPairs.PIP));
			}else if(originalOrderSide==OrderSide.BUY){
				lowerPrice=mainOrderAveragePrice*(1-(stopLossPips*CurrencyPairs.PIP));
				upperPrice=mainOrderAveragePrice*(1+(limitProfitPips*CurrencyPairs.PIP));
			}
			lowerPrice=CurrencyPairs.roundCurrencyPrice(baseCurrency, currency, lowerPrice, BigDecimal.ROUND_UP);
			upperPrice=CurrencyPairs.roundCurrencyPrice(baseCurrency, currency, upperPrice, BigDecimal.ROUND_DOWN);
			
			StringBuffer msg=new StringBuffer("[placeBracketOrders] for "+title+" "+quantityToClose+" avg:"+currency.toString()+" "+mainOrderAveragePrice);
			if(originalOrderSide==OrderSide.BUY){
				msg.append(" low:"+lowerPrice+"("+stopLossPips+"pps)");
				msg.append(" upper:"+upperPrice+"("+limitProfitPips+"pps)");
                if(ForexAnalyzerUtils.isHistoryTrackingEnabled()){
                	ForexAnalyzerUtils.addHistory(new LongPositionOpenedEvent(positionOpenedTimeStamp, "", instrumentSpecification));	
                }
			}else{
				msg.append(" low:"+lowerPrice+"("+limitProfitPips+"pps)");
				msg.append(" upper:"+upperPrice+"("+stopLossPips+"pps)");
                if(ForexAnalyzerUtils.isHistoryTrackingEnabled()){
                	ForexAnalyzerUtils.addHistory(new ShortPositionOpenedEvent(positionOpenedTimeStamp, "", instrumentSpecification));	
                }
			}
			msg.append(" @"+InstrumentAnalyzer.formatTimeStamp(positionOpenedTimeStamp));
			log.info(msg.toString());
			boolean useTrailingStop=false;
			if(originalOrderSide==OrderSide.SELL){
				//lock in profits
				Order lowSideBuyLimit = new Order();
				//order.setOrderType(OrderType.TRAILING_STOP);
				lowSideBuyLimit.setOrderType(OrderType.LIMIT);
				lowSideBuyLimit.setInstrumentSpecification(instrumentSpecification);
				lowSideBuyLimit.setQuantity(quantityToClose);
				//highSideSellLimit.setTrailingDistance(distance);
				lowSideBuyLimit.setLimitPrice(lowerPrice);
				lowSideBuyLimit.setOrderSide(reverseSide);
				limitOrderTracker = ema.getBroker().prepareOrder(lowSideBuyLimit);
				limitOrderTracker.getOrderEventSource().addEventListener(limitOrderListener);
				limitOrderTracker.submit();
				//stop losses if things go bad
				Order highSideBuyStop = new Order();
				if(useTrailingStop){
					highSideBuyStop.setOrderType(OrderType.TRAILING_STOP);
					double distance=upperPrice-mainOrderAveragePrice;
					double roundedDistance=CurrencyPairs.roundCurrencyPrice(baseCurrency, currency, distance, BigDecimal.ROUND_DOWN);
					highSideBuyStop.setTrailingDistance(roundedDistance);
				}else{
					highSideBuyStop.setOrderType(OrderType.STOP);
					highSideBuyStop.setStopPrice(upperPrice);
				}
				highSideBuyStop.setInstrumentSpecification(instrumentSpecification);
				highSideBuyStop.setQuantity(quantityToClose);			
				highSideBuyStop.setOrderSide(reverseSide);
				stopOrderTracker = ema.getBroker().prepareOrder(highSideBuyStop);
				stopOrderTracker.getOrderEventSource().addEventListener(stopOrderListener);
				stopOrderTracker.submit();
//				limitOrderTracker.submit();
			}else if(originalOrderSide==OrderSide.BUY){
				//lock in profits
				Order highSideSellLimit = new Order();
				//order.setOrderType(OrderType.TRAILING_STOP);
				highSideSellLimit.setOrderType(OrderType.LIMIT);
				highSideSellLimit.setInstrumentSpecification(instrumentSpecification);
				highSideSellLimit.setQuantity(quantityToClose);				
				//highSideSellLimit.setTrailingDistance(distance);
				highSideSellLimit.setLimitPrice(upperPrice);
				highSideSellLimit.setOrderSide(reverseSide);
				limitOrderTracker = ema.getBroker().prepareOrder(highSideSellLimit);
				limitOrderTracker.getOrderEventSource().addEventListener(limitOrderListener);
				limitOrderTracker.submit();
				//stop losses if things go bad
				Order lowSideSellStop = new Order();
				if(useTrailingStop){
					lowSideSellStop.setOrderType(OrderType.TRAILING_STOP);
					double distance=mainOrderAveragePrice-lowerPrice;
					double roundedDistance=CurrencyPairs.roundCurrencyPrice(baseCurrency, currency, distance, BigDecimal.ROUND_DOWN);
					lowSideSellStop.setTrailingDistance(roundedDistance);
				}else{
					lowSideSellStop.setOrderType(OrderType.STOP);
					lowSideSellStop.setStopPrice(lowerPrice);
				}
				lowSideSellStop.setInstrumentSpecification(instrumentSpecification);
				lowSideSellStop.setQuantity(quantityToClose);
//				double stopPrice=((quantity*averagePrice)-2*commission)/quantity;
				lowSideSellStop.setOrderSide(reverseSide);
				stopOrderTracker = ema.getBroker().prepareOrder(lowSideSellStop);
				stopOrderTracker.getOrderEventSource().addEventListener(stopOrderListener);
				stopOrderTracker.submit();
//				limitOrderTracker.submit();
			}
		}
		/**
		 * Constructs a Tracker(implements IPositionTracker) using the given order(Order) to set its associated originalOrder(Order) (and submits the given order(Order)):<br/>
		 * - validates and prepares that order(Order) for submission by invoking <code>broker.prepareOrder(order)</code> on the broker(IBroker) and sets the associated mainOrderTracker(IOrderTracker) with the resulting IOrderTracker.<br/>
		 * - registers the associated mainOrderListener(IEventListener&lt;OrderEvent&gt;) to listen for OrderEvents from the associated mainOrderTracker(IOrderTracker) <br/>
		 * - submits the order by invoking <code>mainOrderTracker.submit()</code> on the associated mainOrderTracker(IOrderTracker).
		 * @param order
		 */
		public Tracker(Order order, double oneWayMainOrderQuantity, List<ForexPairStates> forexPairHistory) {
			log.info("[Tracker] placing main "+order.toString());
			this.forexPairHistory=forexPairHistory;
			originalOrder = order;
			this.oneWayMainOrderQuantity=oneWayMainOrderQuantity;
			this.limitOrderMaximumInactivePeriod=CurrencyPairs.getMaxLimitOrderWaitingMinutes(Currency.toClass(originalOrder.getInstrumentSpecification().getSymbol().toString()), originalOrder.getInstrumentSpecification().getCurrency(), originalOrder.getOrderSide())*InstrumentAnalyzer.ONE_MINUTE;
			mainOrderTracker = ema.getBroker().prepareOrder(order);
			mainOrderTracker.getOrderEventSource().addEventListener(mainOrderListener);
			mainOrderTracker.submit();
		}
		/**
		 * <strong>1.</strong> Gets the previous value(boolean) of the associated isClosed(AtomicBoolean) and sets its new value to true.<br/>
		 * If that previous value(boolean) is true, it returns without doing anything else.<br/>
		 * <strong>2.</strong> Tries to cancel (if not filled yet) the associated originalOrder(Order) by invoking <code>mainOrderTracker.cancel()</code> on the associated mainOrderTracker(IOrderTracker).<br/>
		 * <strong>3.</strong> If the associated quantity(double) is greater than 0, it gets the TRAILING_STOP Order from the associated stopOrderTracker(IOrderTracker), sets its
		 * trailingDistance(double) to 0, and updates the pending order by invoking <code>stopOrderTracker.update(order)</code> on the associated stopOrderTracker(IOrderTracker)
		 */
		public void close() {
			if(isClosed.getAndSet(true)) {
//				log.info("[close] close already requested, nothing to do");
				return; // already closed
			}
			// if main order not yet filled, cancel it...
//			log.info("[close] canceling main order");
			mainOrderTracker.cancel();
			if(limitOrderTracker!=null){
//				log.info("[close] canceling limit order");
				limitOrderTracker.cancel();
			}
			//stopOrderTracker.cancel();
			if(mainOrderFilledQuantity > 0.0) {
//				log.info("[close] updatng stop order price to close position right away");
				// have to close the position. Lets do it by moving
				// trailing distance to zero
				Order order = new Order(stopOrderTracker.getOrder());
//				order.setOrderType(OrderType.MARKET);
				//order.setTrailingDistance(0.0);
				Quote liveQuote=instrumentAnalyzer.getLiveQuoteSeriesBag().get().get(0);
				double stopPrice=order.getOrderSide()==OrderSide.BUY? liveQuote.getBidPrice():liveQuote.getAskPrice();//this makes sure the stop order will get fired right away
				order.setStopPrice(stopPrice);
				if (isSimulateOrderUpdatesEnabled()) {
					if (!pendingChainedStopOrderHolder.compareAndSet(null, order)) {
						throw new IllegalStateException("another stop update is pending: can not proceed");
					}
					stopOrderTracker.cancel();
				} else {
					stopOrderTracker.update(order);
				}
				
			}else if(stopOrderTracker!=null){
//				log.info("[close] canceling stop order");
				stopOrderTracker.cancel();
			}
		}
		/**
		 * returns the current value of the associated isClosed(AtomicBoolean)
		 */
		public boolean isClosed() {
			return isClosed.get();
		}
		/**
		 * {@inheritDoc}
		 */
		public double getQuantity() {
			return mainOrderFilledQuantity;
		}

		public boolean hasMainOrderReachedMaximumInactivePeriod(){
			if(mainOrderAcceptedTimeStamp==null){
				return false;
			}
			TimeStamp currentTimeStamp=ema.getTimeStampService().currentTimeStamp();
			if(mainOrderLastExecutionTimeStamp==null && (currentTimeStamp.getNanoseconds()>(mainOrderAcceptedTimeStamp.getNanoseconds()+mainOrderMaximumInactivePeriod)))// no executions at all on the main order for at least an ${mainOrderMaximumInactivePeriod} from the time it was submitted/accepted
			{
				String title=originalOrder.getInstrumentSpecification().getSymbol().toString()+"/"+originalOrder.getInstrumentSpecification().getCurrency();
				log.info(title+" [hasMainOrderReachedMaximumInactivePeriod] no executions at all on the main order");
				return true;
			}
			if(mainOrderLastExecutionTimeStamp!=null && getMainOrderFilledQuantity()<getMainOrderTracker().getOrder().getQuantity() && (currentTimeStamp.getNanoseconds()>(mainOrderLastExecutionTimeStamp.getNanoseconds()+mainOrderMaximumInactivePeriod)))// there was at least one execution on the main order but it happened at least an hour age (from the last one)
			{
				String title=originalOrder.getInstrumentSpecification().getSymbol().toString()+"/"+originalOrder.getInstrumentSpecification().getCurrency();
				log.info(title+" [hasMainOrderReachedMaximumInactivePeriod] some executions occured on the main order, but nothing lately");
				return true;
			}
			return false;
		}
		/**
		 * it will also return false if the order was updated for no limit, or it hasn't been accepted yet.
		 * if none of these is false, returns whether too much time passed between the order acceptance or last execution timestamp
		 * @return
		 */
		public boolean hasLimitOrderReachedMaximumInactivePeriod(){
			if(/**isClosed() || **/limitOrderUpdatedForNoProfit.get()){//limit order was already updated to just close without profits
				return false;
			}
			if(limitOrderAcceptedTimeStamp==null){
				return false;
			}
			TimeStamp currentTimeStamp=ema.getTimeStampService().currentTimeStamp();
			if(limitOrderLastExecutionTimeStamp==null && (currentTimeStamp.getNanoseconds()>(limitOrderAcceptedTimeStamp.getNanoseconds()+limitOrderMaximumInactivePeriod)))// no executions at all on the limit order for at least an ${limitOrderMaximumInactivePeriod} from the time it was submitted/accepted
			{	
//				limitOrderUpdatedForNoProfit.set(true);

					String title=originalOrder.getInstrumentSpecification().getSymbol().toString()+"/"+originalOrder.getInstrumentSpecification().getCurrency();
					log.info(title+" [hasLimitOrderReachedMaximumInactivePeriod] no executions at all on the limit order");
				return true;
			}
			if(limitOrderLastExecutionTimeStamp!=null && getLimitOrderFilledQuantity()<getLimitOrderTracker().getOrder().getQuantity() && (currentTimeStamp.getNanoseconds()>(limitOrderLastExecutionTimeStamp.getNanoseconds()+limitOrderMaximumInactivePeriod)))// there was at least one execution on the limit order but it happened at least an hour age (from the last one)
			{
//				limitOrderUpdatedForNoProfit.set(true);
				String title=originalOrder.getInstrumentSpecification().getSymbol().toString()+"/"+originalOrder.getInstrumentSpecification().getCurrency();
				log.info(title+" [hasLimitOrderReachedMaximumInactivePeriod] some executions occured on the limit order, but nothing lately");
				return true;
			}
			return false;
		}

		public boolean hasStopOrderReachedMaximumInactivePeriod(){
			if(stopOrderAcceptedTimeStamp==null){
				return false;
			}
			TimeStamp currentTimeStamp=ema.getTimeStampService().currentTimeStamp();
			if(stopOrderLastExecutionTimeStamp==null && (currentTimeStamp.getNanoseconds()>(stopOrderAcceptedTimeStamp.getNanoseconds()+stopOrderMaximumInactivePeriod)))// no executions at all on the stop order for at least an ${stopOrderMaximumInactivePeriod} from the time it was submitted/accepted
			{
				String title=originalOrder.getInstrumentSpecification().getSymbol().toString()+"/"+originalOrder.getInstrumentSpecification().getCurrency();
				log.info(title+" [hasStopOrderReachedMaximumInactivePeriod] no executions at all on the stop order");
				return true;
			}
			if(stopOrderLastExecutionTimeStamp!=null && getStopOrderFilledQuantity()<getStopOrderTracker().getOrder().getQuantity() && (currentTimeStamp.getNanoseconds()>(stopOrderLastExecutionTimeStamp.getNanoseconds()+stopOrderMaximumInactivePeriod)))// there was at least one execution on the stop order but it happened at least an hour age (from the last one)
			{
				String title=originalOrder.getInstrumentSpecification().getSymbol().toString()+"/"+originalOrder.getInstrumentSpecification().getCurrency();
				log.info(title+" [hasStopOrderReachedMaximumInactivePeriod] some executions occured on the stop order, but nothing lately");
				return true;
			}
			return false;
		}

		public double getLimitOrderFilledQuantity() {
			return limitOrderFilledQuantity;
		}

		public double getMainOrderFilledQuantity() {
			return mainOrderFilledQuantity;
		}

		public double getStopOrderFilledQuantity() {
			return stopOrderFilledQuantity;
		}

		public double getLimitOrderAveragePrice() {
			return limitOrderFilledQuantity;
		}

		public double getMainOrderAveragePrice() {
			return mainOrderAveragePrice;
		}

		public double getStopOrderAveragePrice() {
			return stopOrderFilledQuantity;
		}

		public double getMainOrderCommission() {
			return mainOrderCommission;
		}

		public double getStopOrderCommission() {
			return stopOrderCommission;
		}

		public double getLimitOrderCommission() {
			return limitOrderCommission;
		}

		public IOrderTracker getLimitOrderTracker() {
			return limitOrderTracker;
		}

		public IOrderTracker getMainOrderTracker() {
			return mainOrderTracker;
		}

		public IOrderTracker getStopOrderTracker() {
			return stopOrderTracker;
		}

		public long getLimitOrderMaximumInactivePeriod() {
			return limitOrderMaximumInactivePeriod;
		}

		public long getMainOrderMaximumInactivePeriod() {
			return mainOrderMaximumInactivePeriod;
		}

		public void setLimitOrderMaximumInactivePeriod(long timeoutNanoseconds) {
			this.limitOrderMaximumInactivePeriod=timeoutNanoseconds;
		}

		public void setMainOrderMaximumInactivePeriod(long timeoutNanoseconds) {
			this.mainOrderMaximumInactivePeriod=timeoutNanoseconds;
		}

		public void setOptimalProfitTarget(double optimalProfitTarget) {
			this.optimalProfitTarget=optimalProfitTarget;			
		}
		
		public TimeStamp getMainOrderSubmittedTimeStamp() {
			return mainOrderSubmittedTimeStamp;
		}
		
		public TimeStamp getStopOrderSubmittedTimeStamp() {
			return stopOrderSubmittedTimeStamp;
		}
		
		public TimeStamp getLimitOrderSubmittedTimeStamp() {
			return limitOrderSubmittedTimeStamp;
		}
		
		public TimeStamp getMainOrderAcceptedTimeStamp() {
			return mainOrderAcceptedTimeStamp;
		}
		
		public TimeStamp getStopOrderAcceptedTimeStamp() {
			return stopOrderAcceptedTimeStamp;
		}
		
		public TimeStamp getLimitOrderAcceptedTimeStamp() {
			return limitOrderAcceptedTimeStamp;
		}
		
		public TimeStamp getMainOrderCanceledTimeStamp() {
			return mainOrderCanceledTimeStamp;
		}
		
		public TimeStamp getStopOrderCanceledTimeStamp() {
			return stopOrderCanceledTimeStamp;
		}
		
		public TimeStamp getLimitOrderCanceledTimeStamp() {
			return limitOrderCanceledTimeStamp;
		}		
		/**
		 * returns the associated mainOrderCompletedAndCommissionsReceivedTimeStamp(TimeStamp)
		 */
		public TimeStamp getMainOrderCompletedAndCommissionsReceivedTimeStamp(){
			return mainOrderCompletedAndCommissionsReceivedTimeStamp;
		}
		/**
		 * returns the associated orderSubmitToAcceptTimeoutPeriod(long)
		 * @return
		 */
		public long getOrderSubmitToAcceptTimeoutPeriod() {
			return orderSubmitToAcceptTimeoutPeriod;
		}
		
		public void setOneWayMainOrderQuantity(double oneWayMainOrderQuantity){
			this.oneWayMainOrderQuantity = oneWayMainOrderQuantity;
		}
		
		public Currency getMainOrderCommissionCurrency() {
			return mainOrderCommissionCurrency;
		}
		
		public Currency getLimitOrderCommissionCurrency() {
			return limitOrderCommissionCurrency;
		}
		
		public Currency getStopOrderCommissionCurrency() {
			return stopOrderCommissionCurrency;
		}
	}
	/**
	 * Creates and returns a Tracker(implements IPositionTracker) using the given order(Order) to set its associated originalOrder(Order) (and submits the given order(Order) asynchronously):<br/>
	 * - validates and prepares that order(Order) for submission by invoking <code>broker.prepareOrder(order)</code> on the broker(IBroker) and sets the associated mainOrderTracker(IOrderTracker) with the resulting IOrderTracker.<br/>
	 * - registers the associated mainOrderListener(IEventListener&lt;OrderEvent&gt;) to listen for OrderEvents from the associated mainOrderTracker(IOrderTracker) <br/>
	 * - submits the order by invoking <code>mainOrderTracker.submit()</code> on the associated mainOrderTracker(IOrderTracker).
	 */
	public IPositionTracker openPosition(Order order, double oneWayMainOrderQuantity,  List<ForexPairStates> forexPairHistory) {
		Tracker tracker=new Tracker(new Order(order), oneWayMainOrderQuantity, forexPairHistory);
//		InstrumentAnalyzer instrumentAnalyzer=InstrumentAnalyzer.getInstrumentAnalyzers().get(order.getInstrumentSpecification());
//		if(instrumentAnalyzer.getAnalyzerPlugin()!=null){
//			AnalyzerPluginInterface analyzerUtils=instrumentAnalyzer.getAnalyzerPlugin();
//			tracker.setLimitOrderMaximumInactivePeriod(analyzerUtils.getWeightedNanoSecondsToWaitOnExecution());
//			tracker.setMainOrderMaximumInactivePeriod(analyzerUtils.getWeightedNanoSecondsToWaitOnExecution());
//			tracker.setOptimalProfitTarget(analyzerUtils.getTargetProfit());
//		}
		return tracker;
	}
	/**
	 * @deprecated
	 */
	public IPositionTracker openPosition(Order order) {
		return openPosition(order, 0, new ArrayList<ForexPairStates>());
	}
	
	public String getOrderEventToolTip(Order order, OrderEvent event, String prefix, boolean includeTime){
		StringBuilder tooltip=prefix!=null?new StringBuilder(prefix):new StringBuilder();
		if (event instanceof OrderAcceptEvent){
			tooltip.append("Accepted:");
			tooltip.append(" "+order.getOrderSide());
			tooltip.append(" "+order.getOrderType());
			if(order.getOrderType()==OrderType.LIMIT){
				tooltip.append(" "+order.getLimitPrice());
			}
			tooltip.append(" Qty:"+order.getQuantity());
		}else if (event instanceof OrderExecutionEvent) {
			OrderExecutionEvent execution = (OrderExecutionEvent) event;
			tooltip.append("Executed: "+execution.getCumulatedQuantity()+" cum.qty. "+execution.getAveragePrice()+" avg. price");
		}else if (event instanceof OrderCommissionEvent) {
			OrderCommissionEvent commission = (OrderCommissionEvent) event;
			if(commission.isFinalCommissionEvent()){
				tooltip.append("Final ");
			}
			tooltip.append("Commission: "+commission.getCommission());
		}else if (event instanceof OrderCancelEvent) {
			OrderCancelEvent cancel = (OrderCancelEvent) event;
			tooltip.append("Cancelled: "+cancel.getMessage());
		}else if (event instanceof OrderUpdateEvent){
			tooltip.append("Updated:");
			tooltip.append(" "+order.getOrderSide());
			tooltip.append(" "+order.getOrderType());
			if(order.getOrderType()==OrderType.LIMIT){
				tooltip.append(" "+order.getLimitPrice());
			}
			tooltip.append(" Qty:"+order.getQuantity());
		}
		if(includeTime){
			tooltip.append(" Time:"+event.getEventTimeStamp().getDate());
		}
		return tooltip.toString();
	}	
}
